package org.luxor.commons.core.utils.ssl;

import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x500.X500NameBuilder;
import org.bouncycastle.asn1.x500.style.BCStyle;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.operator.jcajce.JcaContentVerifierProviderBuilder;
import org.bouncycastle.pkcs.PKCS10CertificationRequest;
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder;
import org.bouncycastle.util.io.pem.PemObject;
import org.bouncycastle.util.io.pem.PemReader;
import org.bouncycastle.util.io.pem.PemWriter;

import java.io.FileWriter;
import java.io.StringReader;
import java.math.BigInteger;
import java.security.*;
import java.security.cert.X509Certificate;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Date;
import java.util.Random;

/**
 * 证书颁发机构（CA证书）
 *
 * @author Mr.yan @date 2024/1/11
 */
public class OpenSslCertificateAuthorityUtils {
    private static final String KEY_ALGORITHM = "RSA";
    private static final String SIGNATURE_ALGORITHM = "SHA256WithRSA";
    private static final int KEY_SIZE = 2048;

    public static final String DEFAULT_ROOT_CA_PUBLIC_KEY_STR = "-----BEGIN PUBLIC KEY-----\n" +
            "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqBnrNWsNI12mj3Pa4jYV\n" +
            "vPaljuHtvmMOmtHruIH4fem3sQyGM9ALcidoK34b4V+sJ/q+dzjNWbiu6UswbBuP\n" +
            "g6XSEoY3fKcQjarp57hPjxvdGusS6XtDAQ1X3RfC2cLFWAamydt4poXcHDdndxQ4\n" +
            "17z1F55Gc+2WKsmXUiiqS8bOG9D/duzY1o0JLw+ixrpASBUNKVeSFalTvke/H5WB\n" +
            "k/rxAuoN7RT1rs+uL5hFMAofJ/XlaPE5axJlfbJwl21Lsvoa2n5lVc+XQb+eESx8\n" +
            "U0wUs/nyJtnNuMlAWnYchKj3K/MxIKZ7gNixQ9TolECePpqLVlRIvSJSkg+UJH79\n" +
            "AQIDAQAB\n" +
            "-----END PUBLIC KEY-----\n";

    public static final String DEFAULT_ROOT_CA_PRIVATE_KEY_STR = "-----BEGIN PRIVATE KEY-----\n" +
            "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCoGes1aw0jXaaP\n" +
            "c9riNhW89qWO4e2+Yw6a0eu4gfh96bexDIYz0AtyJ2grfhvhX6wn+r53OM1ZuK7p\n" +
            "SzBsG4+DpdIShjd8pxCNqunnuE+PG90a6xLpe0MBDVfdF8LZwsVYBqbJ23imhdwc\n" +
            "N2d3FDjXvPUXnkZz7ZYqyZdSKKpLxs4b0P927NjWjQkvD6LGukBIFQ0pV5IVqVO+\n" +
            "R78flYGT+vEC6g3tFPWuz64vmEUwCh8n9eVo8TlrEmV9snCXbUuy+hrafmVVz5dB\n" +
            "v54RLHxTTBSz+fIm2c24yUBadhyEqPcr8zEgpnuA2LFD1OiUQJ4+motWVEi9IlKS\n" +
            "D5Qkfv0BAgMBAAECggEABXLbnx5ibWJIdw9iCaGHqFmVSbGUh26GB2ok+hTKZWhp\n" +
            "MhWuFulYKaHR3FEI3dAAUEZuHIasj0svbjPn9KUbDIEYM04C5+kxXeorjT155LFa\n" +
            "RBhZA1ThudvBHJM711ubEmyaxOG2gi1GEmsQUTmIUyPWjOauMW1CyA3DOCBFztFs\n" +
            "IQKPD9DIospJRi1d9hmIYeJZfyjyNO6Y+Bi6w/bOTbi6lug9N6+dVn0jcMD59ku/\n" +
            "dL4sK/phcIrPs2/6+fYdCCW5Z3KezWz+NLpWifvYEvc0Ocn9hcDlZC4/ljy+bpl0\n" +
            "9ffvTEW1g1KczWySnqYRxRM5bhs9WjobG72AUu5XTQKBgQDXr1vMpS+b4CmLGfFV\n" +
            "YpLRBejsxHYkMkKvX8vZtJ4o3prw2tG50GD118+3dh2M0horLj2Aeu5y/ab0H0rV\n" +
            "Syyqgw1XVEuCot3AlylN1ILVknIzAZexyC4/IjL6uSUgLSeK+MVf9aKfS1dOxMEW\n" +
            "7aTTjVQSUi8vwaAqImXBq/DLLQKBgQDHhacNWtds6vMEET60IfaCKK+e5sz1RPMk\n" +
            "ZBBa1aRPk4l62ktZ0L1lgqS+bYGAzsXF8MltUT9AoxP7NK9xXca7PzL8kQMKtCyp\n" +
            "x1q3uA1WwWuPFf3ybb59fwo7z19tSBApxqtjxwS6yvyf3QCXgbVw1SDuqEsf2xC2\n" +
            "9iSOmZ3NpQKBgGut+afGxGkO1c7Die/2AZRnXe1u/AiAKzjBG9W5zchbsoESzef1\n" +
            "t6KlNufCCWi3sCbktAHD3e384vA3+zVXjZonSW7S/8vdodY34YAg25ziuy7p2aEB\n" +
            "bs1INqlj67wPrxE7t9Z7nM/oXlNN+ZSH2mVfxpxK+ZHfGiZO+/xf6hyVAoGBALaF\n" +
            "pMGJ1cL02CVjZTpCS5DciXRS7TJ+xfMHexKtmGeXr0/svLvIQkrHjdAKu0rLXY9r\n" +
            "a8kOZ0/JH7FRjCGoSPHoqcr1GZREKQuTrICe5cz0mmHGG48YKAgEHKeEWGbPYsHk\n" +
            "1cssOD5tDVdtRGpVJVe4ES1iVX8p1wuY9u5gx0RlAoGAInAIYB0V+tG87By2xseC\n" +
            "Nk214qW/pPyvR0fK+B3NulCtcFNKA5E43/aeJSJavg5XmDGLqVr73pOaYVPKBXrF\n" +
            "l0s8PZeXLEO6JnuR5GMNyito4T3tt+eKBqTJ03ERngVVEx2Nc1WS1g/06z8VbOOF\n" +
            "Z9r+AmYXhcmgR2EEUmbuolw=\n" +
            "-----END PRIVATE KEY-----\n";

    /**
     * 证书颁发机构（CA）根证书
     */
    static KeyPair rootCaKeyPair;
    static X509Certificate rootCaCert;

    static {
        // 使用BouncyCastle作为加密算法实现
        Security.addProvider(new BouncyCastleProvider());
        // CA根证书的密钥对
        rootCaKeyPair = new KeyPair(getX509PublicKey(DEFAULT_ROOT_CA_PUBLIC_KEY_STR), getPKCS8PrivateKey(DEFAULT_ROOT_CA_PRIVATE_KEY_STR));
        // CA根证书
        rootCaCert = generateRootCertificate(rootCaKeyPair);
    }


    public static void main(String[] args) throws Exception {
        // 一、输出CA根证书
        writeX509PemRootCertificate("ca_x509.crt");

        // 二、创建服务端证书
        // 2.1 生成Ssl证书的密钥对
        KeyPair serverKeyPair = keyPairGenerate();
        writePKCS8PemPrivateKey(serverKeyPair.getPrivate(), "server_pkcs8.key");
        // 2.2 Ssl证书签名请求(CSR)
        X500Name subject = new X500NameBuilder(BCStyle.INSTANCE)
                .addRDN(BCStyle.CN, "Netty Server CA")
                .addRDN(BCStyle.OU, "Luxor Development")
                .addRDN(BCStyle.O, "Luxor Inc.")
                .addRDN(BCStyle.C, "CN")
                .build();
        PKCS10CertificationRequest csr = createSslCertificateRequest(subject, serverKeyPair.getPublic());
        writePKCS10PemCertificateRequest(csr, "server_pkcs10.csr");
        // 2.3 签发Ssl证书(CRT)
        X509Certificate crt = OpenSslCertificateAuthorityUtils.signCertificateSigningRequest(csr);
        crt.verify(rootCaCert.getPublicKey());
        writeX509PemCertificate(crt, "server_x509.crt");

        // 三、创建客户端证书
        // 3.1 生成Ssl证书的密钥对
        KeyPair clientKeyPair = keyPairGenerate();
        writePKCS8PemPrivateKey(clientKeyPair.getPrivate(), "client_pkcs8.key");
        // 3.2 Ssl证书签名请求(CSR)
        subject = new X500NameBuilder(BCStyle.INSTANCE)
                .addRDN(BCStyle.CN, "Netty Client CA")
                .addRDN(BCStyle.OU, "Luxor Development")
                .addRDN(BCStyle.O, "Luxor Inc.")
                .addRDN(BCStyle.C, "CN")
                .build();
        csr = createSslCertificateRequest(subject, clientKeyPair.getPublic());
        writePKCS10PemCertificateRequest(csr, "client_pkcs10.csr");
        // 3.3 签发Ssl证书(CRT)
        crt = OpenSslCertificateAuthorityUtils.signCertificateSigningRequest(csr);
        crt.verify(rootCaCert.getPublicKey());
        writeX509PemCertificate(crt, "client_x509.crt");

    }

    /**
     * 创建(CSR)申请文件
     *
     * @param sslSubject   证书持有者信息
     * @param sslPublicKey 证书持有者公钥
     * @return org.bouncycastle.pkcs.PKCS10CertificationRequest
     */
    public static PKCS10CertificationRequest createSslCertificateRequest(X500Name sslSubject, PublicKey sslPublicKey) throws OperatorCreationException {
        // 签名者,既证书颁发机构(CA)
        ContentSigner signer = new JcaContentSignerBuilder(OpenSslCertificateAuthorityUtils.SIGNATURE_ALGORITHM)
                .setProvider(BouncyCastleProvider.PROVIDER_NAME)
                .build(rootCaKeyPair.getPrivate());
        return new JcaPKCS10CertificationRequestBuilder(sslSubject, sslPublicKey).build(signer);
    }

    /**
     * 审核申请文件(CSR)，并颁发SSL证书
     *
     * @param csr 申请文件(CSR)，里面包含证书的持有者和公钥信息
     * @return SSL证书(CRT)
     */
    public static X509Certificate signCertificateSigningRequest(PKCS10CertificationRequest csr) throws Exception {
        if (!csr.isSignatureValid(new JcaContentVerifierProviderBuilder().build(rootCaCert.getPublicKey()))) {
            throw new Exception("CSR is not valid");
        }

        // 创建X509v3CertificateBuilder对象，用于构建证书的各种属性
        X509v3CertificateBuilder certBuilder = new X509v3CertificateBuilder(
                // 签发者
                new X500Name(rootCaCert.getSubjectX500Principal().getName()),
                // 序列号
                BigInteger.valueOf(new Random().nextLong()),
                // 生效日期，这里表示当前时间减去10000毫秒
                new Date(System.currentTimeMillis()),
                // 过期日期，这里表示当前时间加上365天的毫秒数
                new Date(System.currentTimeMillis() + 365L * 24 * 60 * 60 * 1000),
                // 持证者主体
                csr.getSubject(),
                // 持证者公钥KEY
                csr.getSubjectPublicKeyInfo());
        // 添加基本约束扩展,将basicConstraints扩展设置为true，并指定BasicConstraints对象，其中false表示这是一个终端实体证书（End Entity Certificate）。
        certBuilder.addExtension(Extension.basicConstraints, true, new BasicConstraints(false));

        // 签名者,既证书颁发机构(CA)
        ContentSigner signer = new JcaContentSignerBuilder(OpenSslCertificateAuthorityUtils.SIGNATURE_ALGORITHM)
                .setProvider(BouncyCastleProvider.PROVIDER_NAME)
                .build(rootCaKeyPair.getPrivate());

        // 创建一个X.509实体证书
        X509Certificate sslCertificate = new JcaX509CertificateConverter().getCertificate(certBuilder.build(signer));
        // 使用CA的公钥验证生成的实体证书的签名是否有效。这一步用于确保实体证书由CA签发。
        sslCertificate.verify(rootCaCert.getPublicKey());
        return sslCertificate;
    }

    /**
     * 生成CA的根证书
     *
     * @param rootKeyPair CA证书的密钥对
     * @return CA证书(CRT), 里面包含发行者信息和公钥
     */
    @SuppressWarnings("ALL")
    private static X509Certificate generateRootCertificate(KeyPair rootCaKeyPair) {
        try {
            // 创建X500Name对象，用于添加标准的主题信息
            X500Name subject = new X500NameBuilder(BCStyle.INSTANCE)
                    .addRDN(BCStyle.CN, "Luxor Root CA")
                    .addRDN(BCStyle.OU, "Luxor Certification Authority")
                    .addRDN(BCStyle.O, "Luxor Inc.")
                    .addRDN(BCStyle.C, "CN")
                    .build();
            // 创建X509v3CertificateBuilder对象，用于构建证书的各种属性
            X509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder(
                    // 发行者
                    subject,
                    // 序列号
                    BigInteger.valueOf(new Random().nextLong()),
                    // 生效日期，这里表示当前时间减去10000毫秒
                    new Date(System.currentTimeMillis()),
                    // 过期日期，这里表示当前时间加上365天的毫秒数
                    new Date(System.currentTimeMillis() + 365L * 24 * 60 * 60 * 1000),
                    // 主体(证书持有者)
                    subject,
                    // 用于签署证书的公钥
                    rootCaKeyPair.getPublic());

            // 添加基本约束扩展。表示该证书可作为CA（证书颁发机构）签发其他证书。
            certBuilder.addExtension(Extension.basicConstraints, true, new BasicConstraints(true));

            // 使用根证书私钥签署根证书
            X509CertificateHolder certificateHolder = certBuilder.build(new JcaContentSignerBuilder(OpenSslCertificateAuthorityUtils.SIGNATURE_ALGORITHM)
                    .setProvider(BouncyCastleProvider.PROVIDER_NAME)
                    .build(rootCaKeyPair.getPrivate()));

            // 将签署后的根证书，转换为X509Certificate对象
            X509Certificate rootCertificate = new JcaX509CertificateConverter().getCertificate(certificateHolder);

            // 自我验证，确保根证书的有效性。即可以被根据根证书签发的其他证书所信任
            rootCertificate.verify(rootCertificate.getPublicKey());

            return rootCertificate;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static KeyPair keyPairGenerate() throws GeneralSecurityException {
        KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(OpenSslCertificateAuthorityUtils.KEY_ALGORITHM, BouncyCastleProvider.PROVIDER_NAME);
        keyPairGen.initialize(OpenSslCertificateAuthorityUtils.KEY_SIZE);
        return keyPairGen.generateKeyPair();
    }

    public static PublicKey getX509PublicKey(String publicKeyPem) {
        try {
            byte[] publicKey = new PemReader(new StringReader(publicKeyPem)).readPemObject().getContent();
            return KeyFactory.getInstance(OpenSslCertificateAuthorityUtils.KEY_ALGORITHM, BouncyCastleProvider.PROVIDER_NAME)
                    .generatePublic(new X509EncodedKeySpec(publicKey));
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }
    }

    public static PrivateKey getPKCS8PrivateKey(String privateKeyPem) {
        try {
            byte[] privateKey = new PemReader(new StringReader(privateKeyPem)).readPemObject().getContent();
            return KeyFactory.getInstance(OpenSslCertificateAuthorityUtils.KEY_ALGORITHM, BouncyCastleProvider.PROVIDER_NAME)
                    .generatePrivate(new PKCS8EncodedKeySpec(privateKey));
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static void writeX509PemRootCertificate(String fileName) throws Exception {
        try (PemWriter pemWriter = new PemWriter(new FileWriter(fileName))) {
            PemObject crtPem = new PemObject("CERTIFICATE", rootCaCert.getEncoded());
            pemWriter.writeObject(crtPem);
        }
    }

    public static void writeX509PemCertificate(X509Certificate crt, String fileName) throws Exception {
        try (PemWriter pemWriter = new PemWriter(new FileWriter(fileName))) {
            PemObject crtPem = new PemObject("CERTIFICATE", crt.getEncoded());
            pemWriter.writeObject(crtPem);
        }
    }

    public static void writePKCS10PemCertificateRequest(PKCS10CertificationRequest csr, String fileName) throws Exception {
        try (PemWriter pemWriter = new PemWriter(new FileWriter(fileName))) {
            PemObject csrPem = new PemObject("CERTIFICATE REQUEST", csr.getEncoded());
            pemWriter.writeObject(csrPem);
        }
    }

    public static void writePKCS8PemPrivateKey(PrivateKey privateKey, String fileName) throws Exception {
        try (PemWriter pemWriter = new PemWriter(new FileWriter(fileName))) {
            PemObject privateKeyPem = new PemObject("PRIVATE KEY", privateKey.getEncoded());
            pemWriter.writeObject(privateKeyPem);
        }
    }

    public static void writeX509PemPublicKey(PublicKey publicKey, String fileName) throws Exception {
        try (PemWriter pemWriter = new PemWriter(new FileWriter(fileName))) {
            PemObject publicKeyPem = new PemObject("PUBLIC KEY", publicKey.getEncoded());
            pemWriter.writeObject(publicKeyPem);
        }
    }

}
